<template>
  <div class="contain">
    <div class="title">检索增强生成系统</div>
    <div style="flex: 1;">
      <div id="conent_chat">
        <div v-for="(item, key) in chat" :key="key" style="display: flex; align-items: flex-start; margin-bottom: 8px">
          <n-icon-wrapper v-if="key % 2 == 0" :size="30" :border-radius="15" style="flex-shrink: 0">
            <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 12 12">
              <g fill="none">
                <path
                  d="M6 1a2 2 0 1 0 0 4a2 2 0 0 0 0-4zM5 3a1 1 0 1 1 2 0a1 1 0 0 1-2 0zm3.5 3h-5A1.5 1.5 0 0 0 2 7.5c0 1.116.459 2.01 1.212 2.615C3.953 10.71 4.947 11 6 11c1.053 0 2.047-.29 2.788-.885C9.54 9.51 10 8.616 10 7.5A1.5 1.5 0 0 0 8.5 6zm-5 1h5a.5.5 0 0 1 .5.5c0 .817-.325 1.423-.838 1.835C7.636 9.757 6.88 10 6 10c-.88 0-1.636-.243-2.162-.665C3.325 8.923 3 8.317 3 7.5a.5.5 0 0 1 .5-.5z"
                  fill="currentColor"></path>
              </g>
            </svg>
          </n-icon-wrapper>
          <n-icon-wrapper v-else :size="30" :border-radius="15" style="flex-shrink: 0" color="#804000">
            <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
              <g fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                <path
                  d="M7 7h10a2 2 0 0 1 2 2v1l1 1v3l-1 1v3a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-3l-1-1v-3l1-1V9a2 2 0 0 1 2-2z">
                </path>
                <path d="M10 16h4"></path>
                <circle cx="8.5" cy="11.5" r=".5" fill="currentColor"></circle>
                <circle cx="15.5" cy="11.5" r=".5" fill="currentColor"></circle>
                <path d="M9 7L8 3"></path>
                <path d="M15 7l1-4"></path>
              </g>
            </svg>
          </n-icon-wrapper>

          <div v-if="key % 2 == 0" style="
              margin-left: 8px;
              line-height: 30px;
              background-color: #18a05844;
              border-radius: 12px;
              padding: 0 8px;
            ">
            {{ item }}
          </div>
          <div v-else class="answer_card" v-html="marked(item, { sanitize: true })"></div>
        </div>

        <div style="display: flex; margin-left: 38px;">
          <n-popover v-for="item in recalls" style="width: 600px; max-height: 500px;" scrollable trigger="hover">
            <template #trigger>
              <div class="recall" type="success">文件名：{{ item['file_name'] }}
                <br /> 页码：{{ item['page'] }} ｜ 相似度：{{
                  Math.round(item['similarity'] *
                    10000) / 100 }}%
              </div>
            </template>
            <span>{{ item['text'] }}</span>
          </n-popover>

          <!-- 添加反馈功能 -->
          <div v-if="chat.length % 2 == 0 && chat.length > 0 && !qa_loading">
            <n-button :disabled="feedbackState > 0" @click="feedback(1)" tertiary circle style="margin-right: 12px;">
              <template #icon>
                <svg v-if="feedbackState == 1" xmlns="http://www.w3.org/2000/svg"
                  xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32">
                  <path d="M2 16h5v14H2z" fill="currentColor"></path>
                  <path
                    d="M23 30H9V15.197l3.042-4.563l.845-5.917A2.01 2.01 0 0 1 14.867 3H15a3.003 3.003 0 0 1 3 3v6h8a4.005 4.005 0 0 1 4 4v7a7.008 7.008 0 0 1-7 7z"
                    fill="currentColor"></path>
                </svg>
                <svg v-else xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
                  viewBox="0 0 32 32">
                  <path
                    d="M26 12h-6V6a3.003 3.003 0 0 0-3-3h-2.133a2.01 2.01 0 0 0-1.98 1.717l-.845 5.917L8.465 16H2v14h21a7.008 7.008 0 0 0 7-7v-7a4.005 4.005 0 0 0-4-4zM8 28H4V18h4zm20-5a5.006 5.006 0 0 1-5 5H10V17.303l3.958-5.937l.91-6.366H17a1 1 0 0 1 1 1v8h8a2.002 2.002 0 0 1 2 2z"
                    fill="currentColor"></path>
                </svg>
              </template>
            </n-button>
            <n-button :disabled="feedbackState > 0" @click="feedback(2)" tertiary circle>
              <template #icon>
                <svg v-if="feedbackState == 2" xmlns="http://www.w3.org/2000/svg"
                  xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32">
                  <path d="M2 2h5v14H2z" fill="currentColor"></path>
                  <path
                    d="M23 2H9v14.803l3.042 4.563l.845 5.917A2.01 2.01 0 0 0 14.867 29H15a3.003 3.003 0 0 0 3-3v-6h8a4.005 4.005 0 0 0 4-4V9a7.008 7.008 0 0 0-7-7z"
                    fill="currentColor"></path>
                </svg>
                <svg v-else xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
                  viewBox="0 0 32 32">
                  <path
                    d="M30 16V9a7.008 7.008 0 0 0-7-7H2v14h6.465l3.577 5.366l.846 5.917A2.01 2.01 0 0 0 14.868 29H17a3.003 3.003 0 0 0 3-3v-6h6a4.005 4.005 0 0 0 4-4zM8 14H4V4h4zm20 2a2.002 2.002 0 0 1-2 2h-8v8a1 1 0 0 1-1 1h-2.133l-.91-6.366L10 14.697V4h13a5.006 5.006 0 0 1 5 5z"
                    fill="currentColor"></path>
                </svg>
              </template>
            </n-button>
          </div>

        </div>
        <div v-if="chat.length == 0">无对话记录</div>
        <div v-if="chat.length % 2" style="display: flex; align-items: center">
          <Loading />
          <div style="margin-left: 6px">响应中...</div>
        </div>
      </div>
      <n-spin :show="uploadLoading">
        <div style="display: flex;">
          <n-space>
            <n-tag v-for="item in uploadedFiles" closable @close="removeFile(item)">
              {{ item }}
            </n-tag>
          </n-space>
        </div>
        <div style="display: flex; margin-top: 12px">
          <n-input type="text" placeholder="请输入内容" style="text-align: left" v-model:value="qa_input"
            :disabled="qa_loading" @keydown.enter="qa" />
          <n-button style="margin-left: 12px" @click="uploadFile" :disabled="qa_loading">
            <template #icon>
              <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
                <path
                  d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5a2.5 2.5 0 0 1 5 0v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5a2.5 2.5 0 0 0 5 0V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z"
                  fill="currentColor"></path>
              </svg>
            </template>
          </n-button>
          <n-button type="primary" style="margin-left: 12px" @click="qa" :loading="qa_loading">
            提交
          </n-button>
        </div>
        <div style="display: flex; margin-top: 12px">
          <n-button @click="stop" :disabled="!qa_loading || chat.length % 2 != 0">
            停止响应
          </n-button>

          <n-button style="margin-left: 24px" @click="reqa" :disabled="chat.length == 0 || qa_loading">
            重新响应
          </n-button>

          <n-button style="margin-left: 24px" @click="new_chat" :disabled="qa_loading">
            开始新对话
          </n-button>
        </div>
      </n-spin>
    </div>
  </div>
</template>

<script setup>
import { ref } from "vue";
import { useMessage, useNotification } from "naive-ui";
import draggable from "vuedraggable";
import Loading from "./loading.vue";
import { io } from "socket.io-client";
import axios from 'axios';
import { marked } from 'marked'


const message = useMessage();
const noti = useNotification();

let orderId = 0;
const socket = io("/");

const cutHistory = (history) => {
  // 仅保留上一段对话信息
  if (history.length % 2 != 0) {
    throw new Error("history error.")
  }
  if (history.length == 0) {
    return []
  } else {
    return history.slice(history.length - 2, history.length)
  }
}
const chat = ref([]);
const qa_input = ref("");
const qa_loading = ref(false);
const qa = () => {
  if (!qa_input.value) {
    message.warning("输入内容为空");
    return;
  }

  recalls.value = []
  feedbackState.value = 0

  orderId += 1;
  qa_loading.value = true;
  let para = JSON.stringify({
    history: cutHistory(chat.value),
    query: qa_input.value,
    fileNames: uploadedFiles.value,
    orderId: orderId,
  });
  chat.value.push(qa_input.value);
  socket.emit("chat", para);
};
socket.on("message", (msg) => {
  if (orderId == msg["orderId"]) {
    if (chat.value.length % 2 == 1) {
      chat.value.push(msg['content']);
      chat.value[chat.value.length - 1] = chat.value[chat.value.length - 1] + "●"
      qa_input.value = "";
      return;
    }
    if (msg["finish_reason"] == "stop") {
      qa_loading.value = false;
      chat.value[chat.value.length - 1] = chat.value[
        chat.value.length - 1
      ].slice(0, -1);
    } else if (msg["finish_reason"] == "recall") {
      recalls.value = msg['value']
    } else {
      chat.value[chat.value.length - 1] =
        chat.value[chat.value.length - 1].slice(0, -1) + msg["content"] + "●";
      let div = document.getElementById("conent_chat");
      div.scrollTop = div.scrollHeight;
    }
  }
});

const recalls = ref([])
const reqa = () => {
  recalls.value = []
  feedbackState.value = 0

  chat.value.pop();
  let query = chat.value.pop()
  orderId += 1;
  qa_loading.value = true;
  let para = JSON.stringify({
    history: cutHistory(chat.value),
    query: query,
    fileNames: uploadedFiles.value,
    orderId: orderId,
  });
  chat.value.push(query);
  socket.emit("chat", para);
};
const new_chat = () => {
  chat.value = [];
  recalls.value = []
};
const stop = () => {
  orderId += 1;
  chat.value.pop();
  qa_input.value = chat.value.pop();
  qa_loading.value = false;
};


const uploadedFiles = ref([])
const uploadLoading = ref(false)
const uploadFile = () => {
  const input = document.createElement('input');
  input.type = 'file';
  input.style.display = 'none';
  input.multiple = true;
  input.accept = ".doc,.docx,.pdf,.txt,.md";

  input.onchange = (e) => {
    if (e.target.files.length == 0) {
      return
    }
    console.log("选中的文件信息", e.target.files);  // 输出选中的文件信息
    // 可以在这里添加上传文件的逻辑
    const formData = new FormData();
    e.target.files.forEach(element => {
      formData.append('files', element);  // 使用同一个键名 'files'
    });
    uploadLoading.value = true
    message.info("文件解析中")
    axios.post(import.meta.env.MODE == "development" ? 'apis/upload' : '/upload', formData, {
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    })
      .then(response => {
        uploadLoading.value = false
        response.data.upload.forEach(item => {
          if (!uploadedFiles.value.includes(item)) {
            uploadedFiles.value.push(item);
          }
        })
      })
      .catch(error => {
        console.error('上传错误:', error);
      });
  };
  document.body.appendChild(input);
  input.click();
  input.remove();
}
const removeFile = (fileName) => {
  uploadedFiles.value = uploadedFiles.value.filter(item => item != fileName);
}

const feedbackState = ref(0)
// 定义 feedbackState 状态
// 0: 未反馈
// 1: 最佳回复
// 2: 较差回复
const feedback = (type) => {
  axios.post(import.meta.env.MODE == "development" ? 'apis/feedback' : '/feedback', {
    history: chat.value,
    recalls: recalls.value,
    result: String(type)
  }).then(response => {
    feedbackState.value = type
    message.success("反馈成功")
  })
}
</script>

<style lang="less" scoped>
.contain {
  height: 100%;
  position: relative;
  padding: 25px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
}

.title {
  font-size: 1.8em;
  margin-bottom: 30px;
  font-weight: bold;
}

#conent_chat {
  border: 1px solid #99999999;
  margin-bottom: 24px;
  padding: 12px;
  min-height: 260px;
  max-height: 60vh;
  overflow: scroll;
  overflow-x: hidden;
  /* 水平不滚动 */
  overflow-y: auto;

  /* 垂直滚动 */
  &::-webkit-scrollbar {
    display: none;
    /* 隐藏滚动条 */
  }
}

.recall {
  font-size: 0.8em;
  line-height: 1.2em;
  margin-right: 12px;
  background-color: rgba(24, 160, 88, 0.1);
  color: #18a058;
  padding: 2px 5px;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.5s;

  &:hover {
    background-color: rgba(24, 160, 88, 0.3);
    color: #5da07c;
  }
}

.answer_card {
  margin-left: 8px;
  text-align: left;
  background-color: #80400044;
  border-radius: 12px;
  padding: 0 8px;

  :deep(*) {
    margin-block-start: 0.5em;
    margin-block-end: 0.5em;
  }
}
</style>